﻿#if UNITY_EDITOR || UNITY_PS5
#define LOG_MSG
#endif
#if UNITY_PS5
#define STRIP_TIME
#endif
using Devil;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Profiling;

namespace GameToolkit
{
    public partial class AssetBundleUtil
    {
        struct LoadInstruction : IYieldInstruction
        {
            AssetProcessor processor;
            internal LoadInstruction(AssetProcessor proc)
            {
                this.processor = proc;
            }

            public bool YieldContinue()
            {
                return processor == null || (processor.result != ELoadResult.Loading /*&& !processor.HasInvocation*/);
            }
        }

        public abstract class AssetProcessor : IAssetPtr, IExecutableProgress, ICommandHandler
        {
            public static int IdentifyAsset<T>(string path)
            {
                var pid = ParallelUtils.IgnoreCaseToHash(path);
                return MathExt.Hash(pid, typeof(T).GetHashCode());
            }

            public static int IdentifyAsset<T>(int pathId)
            {
                return MathExt.Hash(pathId, typeof(T).GetHashCode());
            }

            public static int IdentifyAsset(int pathId, System.Type type) 
            {
                return MathExt.Hash(pathId, type.GetHashCode());
            }

            public readonly AssetBundleUtil util;
            public EAssetQoS QoS { get; set; }
            public readonly int id;
            public readonly string path;
            internal ELoadResult result;
            internal abstract int dependencyDepth { get; }
            protected ParallelDispatcher.Handler executing;
            public int Id { get { return id; } }
            public abstract bool IsReferenced { get; }
            public string Name { get { return path; } }
            public abstract Object Asset { get; }
            internal abstract System.Type AssetType { get; }
            public abstract float Progress { get; }
            internal virtual void ValidateProgress() { }
            internal abstract bool HasInvocation { get; }
            public abstract ELoadResult LoadResult { get; }
            public AssetProcessor(AssetBundleUtil util, int id, string path)
            {
                this.util = util;
                this.id = id;
                this.path = path;
            }
            public ParallelDispatcher.Handler GetHandler()
            {
                // if(result == ELoadResult.Loading && !executing.IsValid)
                //     Debug.LogErrorFormat("Error Handle State: {0}", path);
                return executing;
            }
            internal abstract void ScheduleCommand(ParallelDispatcher.Handler dependent);
            internal abstract void CreateLoadRequest();
            internal abstract void Unload(UnloadAction forceUnload, ref ErrorHandler errInvoke);
            public abstract void Retain();
            public abstract void Release();
            public override int GetHashCode()
            {
                return id;
            }
        }

        class AbAddress : AssetProcessor, IAssetCache<AssetBundle>, IYieldCommand, IBreakCommandSignal, IIndependentCommandSignal
        {
            readonly object _lockThis = new object();
            public AssetBundle bundle;

            AbAddress[] dependencies;
            public readonly HashSet<AssetProcessor> mLoaded;
            internal AssetHandler<AssetBundle> handler;
            internal ErrorHandler errHandler;
            uint refers;
            uint dependencyRefers;
            AssetBundleCreateRequest req;
            IAssetVariantGroup mVariantGroup;
            //internal string[] assets;
            List<string> mVariants;
            public AssetBundle AssetInstance { get { return bundle; } }
#if LOG_MSG
            long startTime;
            string lastLoadPath;
#endif
            public override bool IsReferenced => refers > 0;
            // 直接依赖包
            internal HashSet<AbAddress> directDependencies;
            internal int _dependencyDepth;
            internal override int dependencyDepth { get { return _dependencyDepth; } }
            internal override bool HasInvocation { get { return handler != null || errHandler != null; } }
            float _progress;
            float _globalProgress;
            public override float Progress
            {
                get
                {
                    if (dependencies == null || dependencies.Length == 0)
                        return _progress;
                    else
                        return _globalProgress;
                }
            }

            System.Action mProgressValidate;
            internal SliceList<string> Variants { get { return new SliceList<string>(mVariants); } }
            internal IAssetVariantGroup VariantGroup => mVariantGroup;
            public override ELoadResult LoadResult { get { return result; } }
            public bool HasVariant => mVariantGroup != null;// { get { return mVariants != null && mVariants.Count > 1; } }
            public override Object Asset { get { return AssetInstance; } }
            public string text => null;
            public byte[] bytes => null;
            internal override System.Type AssetType { get { return typeof(AssetBundle); } }
            public AbAddress(AssetBundleUtil util, int id, string path) : base(util, id, path)
            {
                mLoaded = new HashSet<AssetProcessor>();
#if LOG_MSG
                lastLoadPath = path;
#endif
            }

            internal override void ValidateProgress()
            {
                if (result == ELoadResult.Unload)
                {
                    _progress = 0;
                }
                else if (result == ELoadResult.Success || result == ELoadResult.Faild)
                {
                    _progress = 1f;
                }
                else if (MainThread.IsMainThread)
                {
                    if (req != null)
                        _progress = req.progress;
                }
                else
                {
                    if (mProgressValidate == null)
                        mProgressValidate = () => { if (req != null) _progress = req.progress; };
                    MainThread.RunOnMainThread(mProgressValidate);
                }
            }

            internal void AddVariant(IAssetVariantGroup group, string variant)
            {
                if (mVariants == null)
                    mVariants = new List<string>();
                if (!mVariants.Contains(variant))
                {
                    mVariants.Add(variant);
                    if (mVariantGroup == null)
                        mVariantGroup = group;
                    else if(mVariantGroup != group)
                        Debug.LogWarning($"The ab: {Name} is defined more than one variants' groups ({mVariantGroup.Title}, {group.Title})");
                }
            }

            public void Regist(AssetHandler<AssetBundle> handler, ErrorHandler errHandler)
            {
#if LOG_MSG
                if (Name.EndsWithIgnoreCase(".scene"))
                    RTLog.Log(LogCat.Asset, $"regist {handler.GetDelegateName()} (err: {errHandler.GetDelegateName()}) on ab: \"{Name}\"(ab state: {result})");
#endif
                if (handler != null)
                    this.handler += handler;
                if (errHandler != null)
                    this.errHandler += errHandler;
                if (!executing.IsAlive
                    && (result == ELoadResult.Success || result == ELoadResult.Faild)
                    && (handler != null || errHandler != null))
                {
                    if (MainThread.IsMainThread)
                        InvokeResult();
                    else
                        executing = ParallelUtils.Schedule(InvokeResult, true);// MainThread.RunOnMainThread(InvokeResult);
                }
            }

            public void Unregist(AssetHandler<AssetBundle> handler, ErrorHandler errHandler)
            {
                if (handler != null)
                    this.handler -= handler;
                if (errHandler != null)
                    this.errHandler -= errHandler;
            }

            void InvokeResult()
            {
                if (result == ELoadResult.Success)
                    OnLoadSuccess();
                else if (result == ELoadResult.Faild)
                    OnLoadError(false);
            }


            internal override void ScheduleCommand(ParallelDispatcher.Handler dependency)
            {
                lock (_lockThis)
                {
                    if (result == ELoadResult.Unload)
                    {
                        result = ELoadResult.Loading;
#if LOG_MSG
                        startTime = System.DateTime.UtcNow.Ticks;
#endif
                        executing = ParallelUtils.Schedule(this, false, dependency);
#if UNITY_EDITOR
                        Debug.AssertFormat(string.IsNullOrEmpty(AssertAsset) || !path.ContainsIgnoreCase(AssertAsset), "schedule asset: {0} (ref * {1})", path, refers);
#endif
                    }
                }
            }

            //void ScheduleDependency(int index)
            //{
            //    dependencies[index].ScheduleCommand(default);
            //}

            bool IIndependentCommandSignal.IsBreakable(bool byDependent) { return refers == 0; }

            void IBreakCommandSignal.OnBreak()
            {
                lock (_lockThis)
                {
                    if (result == ELoadResult.Loading)
                    {
                        result = ELoadResult.Unload;
                        OnLoadError(true);
                    }
                }
                RTLog.LogWarningFormat(LogCat.Asset, "abort load \"{0}\"", path);
            } 

            IEnumerator IYieldCommand.Execute()
            {
                // Debug.LogFormat("[Asset] [Debug] Begin \"{0}\"", path);
                var dependents = dependencies == null ? 0 : dependencies.Length;
                for (int i = 0; i < dependents; i++)
                {
                    dependencies[i].ScheduleCommand(default);
                }
                //if (dependents > 0)
                //{
                //    yield return ParallelUtils.ScheduleParallel(ScheduleDependency, dependents);
                //}
                if (result == ELoadResult.Loading)
                {
                    util.mPipeline.Load(this);
                    //yield return new LoadInstruction(this);
                }
#if UNITY_PS5 
                int waitAb = -1;
#endif
                if (dependents > 0)
                {
                    bool isDeploading = true;
                    while (isDeploading && result != ELoadResult.Faild)
                    {
                        isDeploading = result == ELoadResult.Loading;
                        float f = _progress;
                        for (int i = 0; i < dependents; i++)
                        {
                            var dep = dependencies[i];
                            f += dep._progress;
                            if (dep.result == ELoadResult.Loading)
                            {
                                isDeploading = true;
                                //break;
#if UNITY_PS5
                                if (waitAb < i && Name.EndsWithIgnoreCase(".scene"))
                                {
                                    waitAb = i;
                                    Debug.Log($"\"{Name}\" is waiting dependency: \"{dep.Name}\" ({i + 1}/{dependents})");
                                }
#endif
                            }
                        }
                        _globalProgress = f / (float)(1 + dependencies.Length);
                        yield return null;

                    }
                }
                else if (result == ELoadResult.Loading)
                {
                    yield return new LoadInstruction(this);
                }
                //MainThread.RunOnMainThread(InvokeResult);
                if (result > ELoadResult.Loading)
                    yield return ParallelUtils.Schedule(InvokeResult, true);
                _progress = 1f;
                _globalProgress = 1f;
#if LOG_MSG
                if (Name.EndsWithIgnoreCase(".scene"))
                    RTLog.Log(LogCat.Asset, $"complete loading \"{Name}\" progress");
#endif
                //executing = default;
                // Debug.LogFormat("[Asset] [Debug] Finish \"{0}\"", path);
            }

            internal override void Unload(UnloadAction forceUnload, ref ErrorHandler errCallback)
            {
                if ((refers > 0 || dependencyRefers > 0) && forceUnload != UnloadAction.ForceUnload)
                    return;

                executing.Abort();
                executing = default;

                if (result == ELoadResult.Success)
                {
                    foreach (var asset in mLoaded)
                    {
                        asset.Unload(forceUnload, ref errCallback);
                    }
                    mLoaded.Clear();
                    if (bundle != null)
                        bundle.Unload(true);
                    bundle = null;
#if LOG_MSG
                    RTLog.LogFormat(LogCat.Asset, "AssetBundle \"{0}\" was unloaded ({1}).", lastLoadPath, forceUnload);
//#else
//                    RTLog.LogFormat(LogCat.Asset, "AssetBundle \"{0}\" was unloaded ({1}).", path, forceUnload);
#endif
                }
             
                if (result != ELoadResult.Unload)
                {
                    if (errHandler != null)
                    {
                        errCallback += errHandler;
#if LOG_MSG
                        RTLog.LogErrorFormat(LogCat.Asset, "AssetBundle \"{0}\" was interrupted.", lastLoadPath);
#else
                        RTLog.LogErrorFormat(LogCat.Asset, "AssetBundle \"{0}\" was interrupted.", path);
#endif
                    }
                    handler = null;
                    errHandler = null;

                }

                lock (_lockThis)
                {
                    result = ELoadResult.Unload;
                }

            }

            internal void SetDependencies(HashSet<AbAddress> col)
            {
                lock (util.mLock)
                {
                    var preset = dependencies;
                    if (col == null)
                    {
                        dependencies = null;
                    }
                    else
                    {
                        dependencies = new AbAddress[col.Count];
                        col.CopyTo(dependencies);
                        if (refers > 0)
                        {
                            for (int i = 0; i < dependencies.Length; i++)
                            {
                                var ab = dependencies[i];
                                ab.dependencyRefers++;
                                if (ab.dependencyRefers == 1)
                                {
                                    util.mUnloading.Remove(ab);
                                }
                            }
                        }
                    }

                    if (preset != null && refers > 0)
                    {
                        for (int i = 0; i < preset.Length; i++)
                        {
                            preset[i].dependencyRefers--;
                        }
                    }
                }
            }

            public override void Retain()
            {
                bool schedule = false;
                lock (util.mLock)
                {
                    refers++;
                    //Debug.LogFormat("retain \"{0}\"(*{1}).", path, refers);

#if UNITY_EDITOR
                    Debug.AssertFormat(string.IsNullOrEmpty(AssertAsset) || !path.ContainsIgnoreCase(AssertAsset), "retain asset: {0} (ref * {1})", path, refers);
#endif
                    schedule = refers == 1;
                    if (schedule)
                    {
                        util.mUnloading.Remove(this);
                        if (dependencies != null)
                        {
                            for (int i = 0; i < dependencies.Length; i++)
                            {
                                var ab = dependencies[i];
                                ab.dependencyRefers++;

                                if (ab.dependencyRefers + ab.refers == 1)
                                {
                                    util.mUnloading.Remove(ab);
                                }
                            }
                        }
                    }
                }
                if (schedule)
                {
                    ScheduleCommand(util.mPrepareExecution);
                }
            }

            public override void Release()
            {
                bool unload = false;
                lock (util.mLock)
                {
                    if (refers == 0)
                    {
                        throw new System.Exception(string.Format("asset of \"{0}\" was already relesed.", path));
                    }
                    refers--;

#if UNITY_EDITOR
                    Debug.AssertFormat(string.IsNullOrEmpty(AssertAsset) || !path.ContainsIgnoreCase(AssertAsset),
                        "release \"{0}\" (ref: {1}, stat: {2}, {3})", path, refers, result, executing.IsAlive ? "alive" : "");
#endif
                    //Debug.LogFormat("release \"{0}\"(*{1}).", path, refers);
                    if (refers == 0)
                    {
                        if (dependencyRefers == 0)
                        {
                            unload |= util.mUnloading.Add(this);
                            //executing.Abort();
                        }
                        var len = dependencies == null ? 0 : dependencies.Length;
                        if (len > 0)
                        {
                            for (int i = 0; i < len; i++)
                            {
                                var ab = dependencies[i];
                                if (ab.dependencyRefers == 0)
                                {
                                    throw new System.Exception(string.Format("asset of \"{0}\" was already relesed. (by \"{1}\")", ab.path, path));
                                }
                                ab.dependencyRefers--;
                                if (ab.refers == 0 && ab.dependencyRefers == 0)
                                {
                                    unload |= util.mUnloading.Add(ab);
                                    ab.executing.Abort();
                                }
                            }
                        }
                    }
                    unload = unload && util.mUnloadAction < UnloadAction.Unused;
                    if (unload)
                    {
                        util.mUnloadAction = UnloadAction.Unused;
                    }
                }
                //if (unload)
                //{
                //    MainThread.RunOnMainThread(util.UnloadUnusedAsset);
                //}
            }

            public string GetAssetPath()
            {
                if (mVariantGroup != null /*&& mVariants.Count > 0 && mVariantGroup != null*/)
                {
                    for (int i = 0; i < mVariants.Count; i++)
                    {
                        var variant = mVariants[i];
                        if (mVariantGroup.IsActiveVariant(variant))
                            return ParallelUtils.Concat(path, '.', variant);
                    }
                    return ParallelUtils.Concat(path, '.', mVariants[0]);
                }
                return path;
            }

            internal override void CreateLoadRequest()
            {
                lock (_lockThis)
                {
                    if (result == ELoadResult.Loading)
                    {
                        var assetpath = GetAssetPath();
#if LOG_MSG
                        lastLoadPath = assetpath;
#endif

                        req = util.LoadAssetBundle(assetpath);
                        if (req == null)
                        {
                            result = ELoadResult.Faild;
                        }
                        else if (req.isDone)
                        {
                            OnLoadComplete(req);
                        }
                        else
                        {
                            req.completed += OnLoadComplete;
                        }
                    }
                }
            }

            private void OnLoadComplete(AsyncOperation obj)
            {
                if (obj == req)
                {
                    if (req.assetBundle == null)
                    {
                        lock (_lockThis)
                        {
                            result = ELoadResult.Faild;
                        }
                        //OnLoadError();
                    }
                    else
                    {
                        
                        bundle = req.assetBundle;
                        //assets = bundle.GetAllAssetNames(); 
                        lock (_lockThis)
                        {
                            result = ELoadResult.Success;
                        }
                        util.OnLoaded(this);
#if LOG_MSG
                        double sec = (System.DateTime.UtcNow.Ticks - startTime) * ParallelDispatcher.tick_2_seconds;
                        RTLog.LogFormat(LogCat.Asset, "AssetBundle \"{0}\" was loaded ({1} sec, ref *{2}, cmd: {3})",
                            lastLoadPath, sec, refers, executing.IsAlive);
//#else
//                        RTLog.LogFormat(LogCat.Asset, "AssetBundle \"{0}\" was loaded (ref *{1})", path, refers);
#endif
                        
                        //OnLoadSuccess();
                    }
                }
            }

            void OnLoadError(bool abort)
            {
#if LOG_MSG
                if (Name.EndsWithIgnoreCase(".scene"))
                    RTLog.Log(LogCat.Asset, $"invoke failed: {errHandler.GetDelegateName()} on ab: \"{Name}\"");
#endif
                var call = errHandler;

                //result = ELoadResult.Faild;
                req = null;
                errHandler = null;
                handler = null;
#if LOG_MSG
                double sec = (System.DateTime.UtcNow.Ticks - startTime) * ParallelDispatcher.tick_2_seconds;
#endif
                var error = string.Format("Load AssetBundle \"{0}\" faild ({1} sec, ref * {2}{3}).",
#if LOG_MSG
                    lastLoadPath, sec, refers,
#else
                    path, 0, refers,
#endif
                    abort ? ", abort" : ""
                    );
                if (call != null)
                    call(this, error);
                else if(!abort)
                    RTLog.LogError(LogCat.Asset, error);
            }

            void OnLoadSuccess()
            {
#if LOG_MSG
                if (Name.EndsWithIgnoreCase(".scene"))
                    RTLog.Log(LogCat.Asset, $"invoke success: {handler.GetDelegateName()} on ab: \"{Name}\"");
#endif
                var call = handler;
                req = null;
                errHandler = null;
                handler = null;

                call?.Invoke(this, bundle);
            }

            public override string ToString()
            {
                return string.Format("AssetBundle @ \"{0}\"",
#if LOG_MSG
                    lastLoadPath
#else
                    path
#endif
                    );
            }
        }

        enum ECacheAssetType
        {
            Generic,
            Text,
            Bytes,
        }

        class AssetAddress<T> : AssetProcessor, IAssetCache<T>, IYieldCommand, IBreakCommandSignal, IIndependentCommandSignal where T : Object
        {
            readonly object _lockThis = new object();
            bool useAb;
            AbAddress ab;
            ResourceRequest req;
            AssetBundleRequest abReq;
            int pathId;
            T asset;
            ECacheAssetType cacheType;
            internal AssetHandler<T> handler;
            internal ErrorHandler errHandler;
            uint refers;
            bool isManagedAsset;
            public override bool IsReferenced => refers > 0;
            internal override int dependencyDepth { get { return ab == null ? 0 : (ab.dependencyDepth + 1); } }
            public override ELoadResult LoadResult { get { return result; } }
            public override Object Asset { get { return AssetInstance; } }
            internal override bool HasInvocation { get { return handler != null || errHandler != null; } }
            internal override System.Type AssetType { get { return typeof(T); } }
            public string text { get; private set; }
            public byte[] bytes { get; private set; }
            public T AssetInstance
            {
                get
                {
                    if (result == ELoadResult.Unload || (result == ELoadResult.Success && asset == null))
                    {
//#if UNITY_PS5
//                        Debug.LogErrorFormat("[Error] Don't support synchronous asset loading on PS5 ({0})", path);
//                        return null;
//#endif

                        if (!useAb)
                        {
                            asset = Resources.Load<T>(path);
                            if (asset == null)
                            {
                                lock (_lockThis)
                                {
                                    result = ELoadResult.Faild;
                                }
                                OnLoadError(false);
                            }
                            else
                            {
                                lock (_lockThis)
                                {
                                    result = ELoadResult.Faild;
                                }
                                if (cacheType == ECacheAssetType.Bytes)
                                    bytes = (asset as TextAsset).bytes;
                                else if (cacheType == ECacheAssetType.Text)
                                    text = (asset as TextAsset).text;
                                util.OnLoaded(this);
                                OnLoadSuccess();
                            }
                        }
                        else
                        {
                            if (ab == null)
                            {
                                lock (util.mLock)
                                {
                                    var abid = util.mAbAssetMap.GetData(pathId).ab;
                                    ab = util.mAbList.GetValue(abid);
                                    if (refers > 0 && ab != null)
                                    {
                                        ab.Retain();
                                    }
                                }
                            }
                            if (ab != null && ab.bundle != null)
                            {
                                asset = ab.bundle.LoadAsset<T>(path);
                                if (asset == null)
                                {
                                    lock(_lockThis)
                                    {
                                        result = ELoadResult.Faild;
                                    }
                                    OnLoadError(false);
                                }
                                else
                                {
                                    lock (_lockThis)
                                    {
                                        result = ELoadResult.Success;
                                    }
                                    if (cacheType == ECacheAssetType.Bytes)
                                        bytes = (asset as TextAsset).bytes;
                                    else if (cacheType == ECacheAssetType.Text)
                                        text = (asset as TextAsset).text;
                                    util.OnLoaded(this);
                                    OnLoadSuccess();
                                }
                            }
                            else if (asset == null)
                            {
                                OnAbNotReady();
                            }
                        }
                    }
                    return asset;
                }
            }

            public AssetAddress(AssetBundleUtil util, int id, AssetID path, AbAddress ab = null): base(util, id, path.path)
            {
                this.useAb = path.useAb || ab != null;
                this.pathId = path.id;
                this.ab = ab;
                var tp = typeof(T);
                isManagedAsset = !(tp == typeof(GameObject) /*|| tp == typeof(Sprite) || tp == typeof(ScriptableObject)*/);
                if(typeof(T) == typeof(TextAsset))
                {
                    if (path.path.EndsWithIgnoreCase(".bytes"))
                        cacheType = ECacheAssetType.Bytes;
                    else
                        cacheType = ECacheAssetType.Text;
                }
                else
                {
                    cacheType = ECacheAssetType.Generic;
                }
            }

            float _progress;
            public override float Progress
            {
                get
                {
                    if (ab != null)
                        return ab.Progress * 0.7f + _progress * 0.3f;
                    else
                        return _progress;
                }
            }

            internal override void ValidateProgress()
            {
                if (result == ELoadResult.Unload)
                    _progress = 0;
                else if (result == ELoadResult.Success || result == ELoadResult.Faild)
                    _progress = 1f;
                else if (abReq != null)
                    _progress = abReq.progress * 0.99f;
                else if (req != null)
                    _progress = req.progress * 0.99f;
                else
                    _progress = 0.5f;
            }

            bool IIndependentCommandSignal.IsBreakable(bool byDependent) { return refers == 0; }

            internal override void Unload(UnloadAction forceUnload, ref ErrorHandler errInvoke)
            {
                if (refers > 0)
                {
                    if (forceUnload == UnloadAction.Unused)
                        return;
                    else if (forceUnload == UnloadAction.Cleanup && result > ELoadResult.Loading)
                        return;
                }
                //Debug.AssertFormat(result == ELoadResult.Unload, "Unload： {0}", path);
                executing.Abort();

                if (errHandler != null)
                    errInvoke += errHandler;
                if (isManagedAsset && asset != null && (refers == 0 || forceUnload == UnloadAction.ForceUnload))
                {
                    Resources.UnloadAsset(asset);
                }
                result = ELoadResult.Unload;
                asset = null;
                errHandler = null;
                handler = null;
            }

            public override void Retain()
            {
                bool schedule = false;
                lock (util.mLock)
                {
                    refers++;
                    //Debug.LogFormat("retain \"{0}\"(*{1}).", path, refers);

#if UNITY_EDITOR
                    Debug.AssertFormat(string.IsNullOrEmpty(AssertAsset) || !path.ContainsIgnoreCase(AssertAsset), "retain asset: {0} (ref * {1})", path, refers);
#endif
                    schedule = refers == 1;
                    if (schedule)
                    {
                        if (ab != null)
                        {
                            ab.Retain();
                        }
                        //else if (!useAb)
                        util.mUnloading.Remove(this);
                    }
                }
                if(schedule)
                    ScheduleCommand(util.mPrepareExecution);
            }

            public override void Release()
            {
                bool unload = false;
                lock (util.mLock)
                {
                    if (refers == 0)
                        throw new System.Exception(string.Format("asset of \"{0}\" was already relesed.", path));
                    refers--;

#if UNITY_EDITOR
                    Debug.AssertFormat(string.IsNullOrEmpty(AssertAsset) || !path.ContainsIgnoreCase(AssertAsset),
                        "release \"{0}\" (ref: {1}, stat: {2}, {3})", path, refers, result, executing.IsAlive ? "alive" : "");
#endif
                    //Debug.LogFormat("release \"{0}\"(*{1}).", path, refers);
                    if (refers == 0)
                    {
                        if (ab != null)
                        {
                            //#if UNITY_EDITOR
                            //                            Debug.AssertFormat(string.IsNullOrEmpty(AssertAsset) || !ab.path.ContainsIgnoreCase(AssertAsset), "release \"{0}\" by \"{1}\"", ab.path, path);
                            //#endif
                            ab.Release();
                        }
                        //executing.Abort();
                        if (/*!useAb && */result == ELoadResult.Success)
                        {
                            unload |= util.mUnloading.Add(this);
                        }
                    }
                    unload = unload && util.mUnloadAction <= UnloadAction.Unused;
                    if (unload)
                    {
                        util.mUnloadAction = UnloadAction.Unused;
                    }
                }
                //if (unload)
                //    MainThread.RunOnMainThread(util.UnloadUnusedAsset);
            }

            public void Regist(AssetHandler<T> handler, ErrorHandler errHandler)
            {
                if (handler != null)
                    this.handler += handler;
                if (errHandler != null)
                    this.errHandler += errHandler;
                if (!executing.IsAlive
                    && (result == ELoadResult.Success || result == ELoadResult.Faild)
                    && (handler != null || errHandler != null))
                {
                    if (MainThread.IsMainThread)
                        InvokeResult();
                    else
                        executing = ParallelUtils.Schedule(InvokeResult, true);// MainThread.RunOnMainThread(InvokeResult);
                }
            }

            public void Unregist(AssetHandler<T> handler, ErrorHandler errHandler)
            {
                if (handler != null)
                    this.handler -= handler;
                if (errHandler != null)
                    this.errHandler -= errHandler;
            }

            void InvokeResult()
            {
                if (result == ELoadResult.Success)
                    OnLoadSuccess();
                else if (result == ELoadResult.Faild)
                    OnLoadError(false);
            }

            internal override void ScheduleCommand(ParallelDispatcher.Handler dependency)
            {
                lock (_lockThis)
                {
                    if (result == ELoadResult.Unload || result == ELoadResult.Success && ParallelUtils.IsMainThread && asset == null)
                    {
                        result = ELoadResult.Loading;
                        executing = ParallelUtils.Schedule(this, false, dependency);
#if UNITY_EDITOR
                        Debug.AssertFormat(string.IsNullOrEmpty(AssertAsset) || !path.ContainsIgnoreCase(AssertAsset), "schedule asset: {0} (ref * {1})", path, refers);
#endif
                    }
                }
            }

            void IBreakCommandSignal.OnBreak()
            {
                lock (_lockThis)
                {
                    if (result == ELoadResult.Loading)
                    {
                        result = ELoadResult.Unload;
                        OnLoadError(true);
                    }
                }
                RTLog.LogWarningFormat(LogCat.Asset, "abort load \"{0}\"", path);
            }

            IEnumerator IYieldCommand.Execute()
            {
                // Debug.LogFormat("[Asset] [Debug] Begin \"{0}\"", path);

                if (result == ELoadResult.Loading && useAb)
                {
                    if (ab == null)
                    {
                        lock (util.mLock)
                        {
                            var abid = util.mAbAssetMap.GetData(pathId).ab;
                            ab = util.mAbList.GetValue(abid);
                            if (refers > 0 && ab != null)
                            {
                                ab.Retain();
                            }
                        }
                    }
                    if (ab != null)
                    {
                        ab.ScheduleCommand(default);
                        yield return ab;
                    }
                    else
                    {
                        result = ELoadResult.Unload;
                        if (errHandler != null || handler != null)
                            yield return ParallelUtils.Schedule(OnAbNotReady, true);
                    }
                }
                if (result == ELoadResult.Loading)
                {
                    if (ab != null && ab.LoadResult == ELoadResult.Loading)
                        Debug.LogErrorFormat("Error State from {0} (ab: {1})", path, ab.path);
                    util.mPipeline.Load(this);
                    yield return new LoadInstruction(this);
                }
                if (result > ELoadResult.Loading)
                    yield return ParallelUtils.Schedule(InvokeResult, true);
                _progress = 1f;
                //executing = default;
                // Debug.LogFormat("[Asset] [Debug] Finish \"{0}\"", path);
            }

            internal override void CreateLoadRequest()
            {
                if (result == ELoadResult.Loading)
                {
                    if (!useAb)
                    {
                        req = Resources.LoadAsync<T>(path);
                        if (req == null)
                        {
                            lock (_lockThis)
                            {
                                result = ELoadResult.Faild;
                            }
                            OnLoadError(false);
                        }
                        else if (req.isDone)
                        {
                            LoadComplete(req);
                        }
                        else
                        {
                            req.completed += LoadComplete;
                        }
                    }
                    else if (ab != null && ab.bundle != null)
                    {
                        abReq = ab.bundle.LoadAssetAsync<T>(path);

                        if (abReq == null)
                        {
                            lock (_lockThis)
                            {
                                result = ELoadResult.Faild;
                            }
                            OnLoadError(false);
                        }
                        else if (abReq.isDone)
                        {
                            LoadComplete(abReq);
                        }
                        else
                        {
                            abReq.completed += LoadComplete;
                        }
                    }
                    else
                    {
                        abReq = null;
                        req = null;
                        OnAbNotReady();
                        lock (_lockThis)
                        {
                            result = ELoadResult.Unload;
                        }
                    }
                }
            }

            private void LoadComplete(AsyncOperation obj)
            {
                if (obj == abReq)
                {
                    ab.mLoaded.Add(this);
                    asset = abReq.asset as T;
                    if (asset == null)
                    {
                        lock (_lockThis)
                        {
                            result = ELoadResult.Faild;
                        }
                        //OnLoadError();
                    }
                    else
                    {
                        lock (_lockThis)
                        {
                            result = ELoadResult.Success;
                        }
                        if (cacheType == ECacheAssetType.Bytes)
                            bytes = (asset as TextAsset).bytes;
                        else if (cacheType == ECacheAssetType.Text)
                            text = (asset as TextAsset).text;
                        util.OnLoaded(this);
                        //OnLoadSuccess();
                    }
                }
                else if (obj == req)
                {
                    asset = req.asset as T;
                    if (asset == null)
                    {
                        lock (_lockThis)
                        {
                            result = ELoadResult.Faild;
                        }
                        //OnLoadError();
                    }
                    else
                    {
                        lock (_lockThis)
                        {
                            result = ELoadResult.Success;
                        }

                        if (cacheType == ECacheAssetType.Bytes)
                            bytes = (asset as TextAsset).bytes;
                        else if (cacheType == ECacheAssetType.Text)
                            text = (asset as TextAsset).text;
                        util.OnLoaded(this);
                        //OnLoadSuccess();
                    }
                }
            }

            void OnLoadSuccess()
            {
                var call = handler;
                handler = null;
                errHandler = null;
                abReq = null;
                req = null;
//#if UNITY_EDITOR
//                RTLog.LogFormat(LogCat.Asset, "{0} was loaded.", this);
//#endif
                call?.Invoke(this, asset);
            }

            void OnLoadError(bool abort)
            {
                var call = errHandler;
              
                errHandler = null;
                handler = null;
                abReq = null;
                req = null;

                var error = string.Format("Load \"{0}\" as <{1}> error{2}.", path, typeof(T).Name, abort ? "(abort)" : "");
                if (call != null)
                    call(this, error);
                else if (!abort)
                    RTLog.LogError(LogCat.Asset, error);
            }

            void OnAbNotReady()
            {
                var call = errHandler;

                //result = ELoadResult.Unload;
                errHandler = null;
                handler = null;

                var error = string.Format("Load \"{0}\" as <{1}> error. (assetbundle not ready)", path, typeof(T).Name);
                if(call == null)
                    RTLog.LogError(LogCat.Asset, error);
                else
                    call(this, error);
            }

            public override string ToString()
            {
                return string.Format("Asset<{0}> @ \"{1}::{2}\"", typeof(T).Name, useAb ? (ab == null ? "[Unknown]" : ab.Name) : "[Res]", path);
            }
        }

        struct LoadCmd
        {
            public AssetProcessor processor;
            public EAssetQoS qos;
        }

        class LoadAssetPipeline : ILoopCommand, IBreakCommandSignal, IYieldInstruction
        {
            // 批量卸载资源临界值
            readonly int UNLOAD_THRESHOLD = 256;
#if LOG_MSG
            readonly string profiler = "validate assets' progress";
#endif
            readonly object sync_lock = new object();
            //readonly int mBatchLoadCount = 32;
            //Queue<AssetProcessor> mToLoad;
            RandomList<AssetProcessor> mIsLoading;
            AssetBundleUtil mUtil;
            MinHeap<LoadCmd> mToLoad;
            int mUnloadDelay;
            ParallelDispatcher.Handler mLoop;
            RandomList<AssetProcessor> mProcessValidator;

            internal LoadAssetPipeline(AssetBundleUtil util)
            {
                mUtil = util;
                //mToLoad = new Queue<AssetProcessor>(32);
                mToLoad = new MinHeap<LoadCmd>(32, CompareQos, (a, b) => a.processor == b.processor);
                var bufferSize = Mathf.Max(sMaxLoadBudget, 8);
                mProcessValidator = new RandomList<AssetProcessor>(bufferSize, null, true);
                mIsLoading = new RandomList<AssetProcessor>(bufferSize, (a, b) => a == b, true);
            }

            bool IYieldInstruction.YieldContinue()
            {
                return mIsLoading.Count == 0 && mToLoad.Count == 0;
            }

#if LOG_MSG
            public override string ToString()
            {
                return "LoadAssetPipeline";
            }
#endif
            // 比较加载优先级
            int CompareQos(LoadCmd a, LoadCmd b)
            {
                var q = b.qos - a.qos;
                if (q == 0)
                    return a.processor.dependencyDepth - b.processor.dependencyDepth;
                else
                    return q;
            }

            public bool IsLooping { get { return mLoop.IsAlive; } }

            public void Load(AssetProcessor asset)
            {
                lock (sync_lock)
                {
                    LoadCmd cmd;
                    cmd.qos = asset.QoS;
                    cmd.processor = asset;
                    mToLoad.Add(cmd);
                    if (!IsLooping)
                    {
                        mLoop = ParallelUtils.Schedule(this, true);//  MainThread.StartLooper(this);
                        if (mLoop.IsAlive)
                            RTLog.Log(LogCat.Asset, "Start Load Asset Pipeline");
                    }
                }
            }

            public void Execute(float deltaTime)
            {
#if STRIP_TIME
                long ticks = System.DateTime.UtcNow.Ticks;
#endif

                mProcessValidator.Clear();
                lock (sync_lock)
                {
                    var count = mIsLoading.Count;
                    for (int i = count - 1; i >= 0; i--)
                    {
                        var asset = mIsLoading[i];
                        if (asset.result != ELoadResult.Loading)
                            mIsLoading.RemoveAt(i);
                        else
                            mProcessValidator.Add(asset);
                    }
                    if (count > 0 && mIsLoading.Count == 0)
                        mUnloadDelay = 2;
                }
                // UWA: 当前帧或前几帧大量 异步资源加载可能导致 Loading.LockPersistentManager 大量开销。
                var shouldUnload = mUtil.mUnloadAction != UnloadAction.None;
                if(shouldUnload)
                {
                    shouldUnload = mUtil.mUnloadAction == UnloadAction.ForceUnload || mToLoad.Count == 0 || mUtil.mUnloading.Count > UNLOAD_THRESHOLD;
                }
                if (shouldUnload && mUnloadDelay <= 0)
                {
                    mUtil.UnloadUnusedAsset();
                    mUnloadDelay = 2;
                    //return;
                }
                if (mUnloadDelay > 0)
                {
                    mUnloadDelay--;
                }
                else // if(!mUtil.IsAssetsPipelinePaused)
                {
                    lock (sync_lock)
                    {
                        while (!shouldUnload && mIsLoading.Count < sMaxLoadBudget && mToLoad.Count > 0)
                        {
                            var asset = mToLoad.RemoveTop().processor;

                            //if (SubThreading.isDebugBuild)
                            //    Debug.LogFormat("create Load Request: {0}", asset.Name);
                            mIsLoading.Add(asset);
                            asset.CreateLoadRequest();

#if STRIP_TIME
                            if (System.DateTime.UtcNow.Ticks - ticks >= sStripTicks)
                                break;
#endif
                            //Debug.LogFormat("Load {0}", asset.Name);
                        }
                    }
                }
#if UNITY_EDITOR
                Profiler.BeginSample(profiler);
#endif
                //IsLooping = mToLoad.Count > 0 || mIsLoading.Count > 0;
                try
                {
                    for (int i = 0; i < mProcessValidator.Count; i++)
                    {
                        mProcessValidator[i].ValidateProgress();
#if STRIP_TIME
                        var time = System.DateTime.UtcNow.Ticks - ticks;
                        if (time > sStripTicks)
                        {
                            Debug.LogWarning($"[Error] It's taking too long ({time * ParallelDispatcher.tick_2_milisecs}ms) to invoke assets' process and will stop progress refresh");
                            break;
                        }
#endif
                    }
                }
                catch (System.Exception e)
                {
                    Debug.LogException(e);
                }
#if UNITY_EDITOR
                Profiler.EndSample();
#endif
            }

            void IBreakCommandSignal.OnBreak()
            {
                RTLog.Log(LogCat.Asset, "Stop Load Asset Pipeline");
            }
        }
    
    }
}